package com.hmall.gateway.route;

import cn.hutool.json.JSONUtil;
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.hmall.common.utils.CollUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;

@Component
public class DynamicRouteListener {
    @Autowired
    private NacosConfigManager nacosConfigManager;
    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;

    //手动创建一个Set来维护老的路由表,记录每个路由的id,方便配个Nacos的SDK方法清空
    private static final Set<String> ROUTE_ID = new HashSet<>();
    private static final String DATA_ID = "gateway-route.json";
    private static final String GROUP = "DEFAULT_GROUP";
    private static final Long TIMEOUT = 30000L;

    // 要添加监听器,则需要项目启动的时候就开始监听
    @PostConstruct
    public void init() throws NacosException {
        // 获取配置,并添加监听器; 项目启动的时候,需要先把配置拉下来保存到路由表中,然后获取监听器监听
        // getConfigService()拉取配置; getConfigAndSignListener()获取监听器,开始监听
        String config = nacosConfigManager.getConfigService().getConfigAndSignListener(DATA_ID, GROUP, TIMEOUT, new Listener() {
            @Override
            public Executor getExecutor() {
                // 如果需要用线程池来执行,那么就提供一个; 自己创建一个线程池返回;
                // 我们这里不用线程池,所以就不写这个方法
                return null;
            }

            @Override
            public void receiveConfigInfo(String configInfo) {
                //收到配置变化,更新写到路由表
                // 我们这里封装一个方法用来执行业务逻辑
                updateConfig(configInfo);
            }
        });
        // 上面的方法有一个String返回值返回的是拉下来的配置,依然调用updateConfig方法更新写到路由表中
        updateConfig(config);
    }
    private void updateConfig(String config) {
        // 1. 先把字符串转成我们想要的对象
        // 因为这个字符串config是一个JSON形式的集合,所以转为List<RouteDefinition>这是路由表底层的数据结构
        List<RouteDefinition> list = JSONUtil.toList(config, RouteDefinition.class);
        // 2. 更新路由表: 注入RouteDefinitionWrite
        // 由于SDK中的routeDefinitionWriter没有提供全部删除的方法,所以老的路由信息,必须我们自己维护
        for (String id : ROUTE_ID){
            // 遍历我们维护的路由表,按id一个个删除Nacos中的路由表
            routeDefinitionWriter.delete(Mono.just(id)).subscribe();
        }
        //Set也要清空
        ROUTE_ID.clear();
        //然后再把更新的路由表添加进来
        if (CollUtils.isNotEmpty(list)){
            for (RouteDefinition routeDefinition : list) {
                // 写入到路由表: Nacos + 我们自己维护的
                routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
                ROUTE_ID.add(routeDefinition.getId());
            }
        }
    }
}
